D:\a\tools.proto\tools.proto\compiler\src\gen\template\core.rs
Line | Count | Source |
1 | | // Copyright (c) 2024, BlockProject 3D |
2 | | // |
3 | | // All rights reserved. |
4 | | // |
5 | | // Redistribution and use in source and binary forms, with or without modification, |
6 | | // are permitted provided that the following conditions are met: |
7 | | // |
8 | | // * Redistributions of source code must retain the above copyright notice, |
9 | | // this list of conditions and the following disclaimer. |
10 | | // * Redistributions in binary form must reproduce the above copyright notice, |
11 | | // this list of conditions and the following disclaimer in the documentation |
12 | | // and/or other materials provided with the distribution. |
13 | | // * Neither the name of BlockProject 3D nor the names of its contributors |
14 | | // may be used to endorse or promote products derived from this software |
15 | | // without specific prior written permission. |
16 | | // |
17 | | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
18 | | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
19 | | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
20 | | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
21 | | // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
22 | | // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
23 | | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
24 | | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
25 | | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
26 | | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
27 | | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 | | |
29 | | use crate::gen::codec::CodecMap; |
30 | | use crate::gen::template::options::Options; |
31 | | use crate::gen::template::parse_tree::{Component, Fragment, FragmentMode, Token}; |
32 | | use crate::gen::template::Error; |
33 | | use itertools::Itertools; |
34 | | use std::borrow::Cow; |
35 | | use std::collections::HashMap; |
36 | | |
37 | | pub struct Template<'fragment, 'variable> { |
38 | | fragments: HashMap<String, Fragment<'fragment>>, |
39 | | variables: HashMap<&'variable str, Cow<'variable, str>>, |
40 | | } |
41 | | |
42 | | impl<'fragment, 'variable> Template<'fragment, 'variable> { |
43 | 150 | pub fn compile(data: &'fragment [u8]) -> Result<Self, Error> { |
44 | 150 | Self::compile_with_options(data, &Options::default()) |
45 | 150 | } |
46 | | |
47 | 396 | pub fn compile_with_options(data: &'fragment [u8], options: &Options) -> Result<Self, Error> { |
48 | 396 | Self::compile_with_options_includes(data, &CodecMap::default(), options) |
49 | 396 | } |
50 | | |
51 | 10 | pub fn compile_with_includes(data: &'fragment [u8], codecs: &CodecMap<'fragment, '_>) -> Result<Self, Error> { |
52 | 10 | Self::compile_with_options_includes(data, codecs, &Options::default()) |
53 | 10 | } |
54 | | |
55 | 406 | pub fn compile_with_options_includes( |
56 | 406 | data: &'fragment [u8], |
57 | 406 | codecs: &CodecMap<'fragment, '_>, |
58 | 406 | options: &Options, |
59 | 406 | ) -> Result<Self, Error> { |
60 | 406 | let mut fragments = HashMap::new(); |
61 | 406 | let mut frag_stack = Vec::new(); |
62 | 1.13M | let lines = data.split(|v| *v == b'\n')406 ; |
63 | 35.4k | for line35.0k in lines { |
64 | 35.0k | if line.is_empty() { Branch (64:16): [True: 285, False: 34.6k]
Branch (64:16): [True: 1, False: 176]
|
65 | 286 | continue; |
66 | 34.7k | } |
67 | 34.7k | let line = if line[line.len() - 1] == b'\r' { Branch (67:27): [True: 34.4k, False: 120]
Branch (67:27): [True: 176, False: 0]
|
68 | 34.6k | &line[..line.len() - 1] |
69 | | } else { |
70 | 120 | line |
71 | | }; |
72 | 34.7k | if line.is_empty() { Branch (72:16): [True: 1.97k, False: 32.6k]
Branch (72:16): [True: 19, False: 157]
|
73 | 1.99k | continue; |
74 | 32.7k | } |
75 | 32.7k | if line.starts_with(b"#include ") && frag_stack.is_empty()10 { Branch (75:16): [True: 10, False: 32.6k]
Branch (75:50): [True: 10, False: 0]
Branch (75:16): [True: 0, False: 157]
Branch (75:50): [True: 0, False: 0]
|
76 | 10 | let name = std::str::from_utf8(&line[9..]).map_err(|_| Error::InvalidUTF80 )?0 ; |
77 | 10 | let template = codecs.get(name).ok_or_else(|| Error::IncludeNotFound(name.into())0 )?0 ; |
78 | 60 | fragments.extend(template.fragments.iter().map(10 |(k, v)| (k.clone(), v.clone()))); |
79 | 32.7k | } else if line.starts_with(b"#fragment push ") { Branch (79:23): [True: 4.63k, False: 27.9k]
Branch (79:23): [True: 8, False: 149]
|
80 | 4.64k | let fragment = std::str::from_utf8(&line[15..]).map_err(|_| Error::InvalidUTF80 )?0 ; |
81 | 4.64k | if let Some(id169 ) = fragment.find(":") { Branch (81:24): [True: 169, False: 4.46k]
Branch (81:24): [True: 0, False: 8]
|
82 | 169 | let name = &fragment[..id]; |
83 | 169 | let mode = &fragment[id + 1..]; |
84 | 169 | frag_stack.push(Fragment { |
85 | 169 | name, |
86 | 169 | content: Vec::new(), |
87 | 169 | mode: FragmentMode::from_str(mode).ok_or_else(|| Error::UnknownFragmentMode(mode.into())0 )?0 , |
88 | | }); |
89 | 4.47k | } else { |
90 | 4.47k | frag_stack.push(Fragment { |
91 | 4.47k | name: fragment, |
92 | 4.47k | content: Vec::new(), |
93 | 4.47k | mode: FragmentMode::Default, |
94 | 4.47k | }); |
95 | 4.47k | } |
96 | 28.1k | } else if line.starts_with(b"#fragment pop") { Branch (96:23): [True: 4.63k, False: 23.3k]
Branch (96:23): [True: 8, False: 141]
|
97 | 4.64k | if frag_stack.is_empty() { Branch (97:20): [True: 0, False: 4.63k]
Branch (97:20): [True: 0, False: 8]
|
98 | 0 | return Err(Error::InvalidPop); |
99 | 4.64k | } |
100 | 8.71k | let combined_name = frag_stack.iter().map(|v| v.name).join("."); |
101 | 4.64k | //SAFETY: this is fine because the fragment stack must not be empty at this point. |
102 | 4.64k | let mut fragment = unsafe { frag_stack.pop().unwrap_unchecked() }; |
103 | 4.64k | if options.is_fragment_disabled(&combined_name) { Branch (103:20): [True: 4, False: 4.63k]
Branch (103:20): [True: 0, False: 8]
|
104 | 4 | fragment.content.clear(); |
105 | 4.64k | } |
106 | 4.64k | fragments.insert(combined_name, fragment); |
107 | | } else { |
108 | 23.4k | let cur_fragment = frag_stack.last_mut().ok_or(Error::NoFragment)?0 ; |
109 | 23.4k | let mut token = Token::new(line); |
110 | 901k | while token.has_next() { Branch (110:23): [True: 872k, False: 23.3k]
Branch (110:23): [True: 5.19k, False: 141]
|
111 | 877k | if token.cur() == b'{' { Branch (111:24): [True: 24.0k, False: 848k]
Branch (111:24): [True: 90, False: 5.10k]
|
112 | 24.1k | if token.next() == Some(b'{') { Branch (112:28): [True: 5.84k, False: 18.2k]
Branch (112:28): [True: 44, False: 46]
|
113 | 5.89k | token.inc(); |
114 | 18.2k | } |
115 | 24.1k | if let Some(component22.5k ) = token.pop()?0 .map(Component::Constant) { Branch (115:32): [True: 22.4k, False: 1.63k]
Branch (115:32): [True: 90, False: 0]
|
116 | 22.5k | cur_fragment.content.push(component); |
117 | 22.5k | }1.63k |
118 | 853k | } else if token.cur() == b'}' { Branch (118:31): [True: 24.0k, False: 824k]
Branch (118:31): [True: 90, False: 5.01k]
|
119 | 24.1k | if token.next() == Some(b'}') { Branch (119:28): [True: 5.84k, False: 18.2k]
Branch (119:28): [True: 44, False: 46]
|
120 | 5.89k | token.inc(); |
121 | 5.89k | if let Some(component) = token.pop()?0 .map(Component::Constant) { Branch (121:36): [True: 5.84k, False: 0]
Branch (121:36): [True: 44, False: 0]
|
122 | 5.89k | cur_fragment.content.push(component); |
123 | 5.89k | }0 |
124 | 18.2k | } else if let Some(component) = Branch (124:39): [True: 18.2k, False: 0]
Branch (124:39): [True: 46, False: 0]
|
125 | 18.2k | token.pop()?0 .map(|v| Component::parse_variable(options.functions(), v)) |
126 | | { |
127 | 18.2k | cur_fragment.content.push(component?0 ); |
128 | 0 | } |
129 | 829k | } |
130 | 877k | token.inc() |
131 | | } |
132 | 23.4k | if let Some(component10.8k ) = token.pop()?0 .map(Component::Constant) { Branch (132:24): [True: 10.7k, False: 12.5k]
Branch (132:24): [True: 63, False: 78]
|
133 | 10.8k | cur_fragment.content.push(component); |
134 | 12.6k | } |
135 | 23.4k | cur_fragment.content.push(Component::NewLine); |
136 | | } |
137 | | } |
138 | 406 | Ok(Template { |
139 | 406 | fragments, |
140 | 406 | variables: HashMap::new(), |
141 | 406 | }) |
142 | 406 | } |
143 | | |
144 | 1.21k | pub fn var(&mut self, key: &'variable str, value: impl Into<Cow<'variable, str>>) -> &mut Self { |
145 | 1.21k | self.variables.insert(key, value.into()); |
146 | 1.21k | self |
147 | 1.21k | } |
148 | | |
149 | 66 | pub fn var_d(&mut self, key: &'variable str, value: impl ToString) -> &mut Self { |
150 | 66 | self.variables.insert(key, value.to_string().into()); |
151 | 66 | self |
152 | 66 | } |
153 | | |
154 | 2.50k | fn render_internal( |
155 | 2.50k | &self, |
156 | 2.50k | variables: &HashMap<&str, Cow<str>>, |
157 | 2.50k | path: &str, |
158 | 2.50k | fragments: &[&str], |
159 | 2.50k | ) -> Result<String, Error> { |
160 | 2.50k | let mut rendered = Vec::new(); |
161 | 5.25k | for name2.74k in fragments { |
162 | 2.74k | let name: Cow<str> = match path.is_empty() { |
163 | 1.78k | false => Cow::Owned(format!("{}.{}", path, name)), |
164 | 959 | true => Cow::Borrowed(name), |
165 | | }; |
166 | 2.74k | let fragment = self.fragments.get(&*name).ok_or_else(|| Error::FragmentNotFound(String::from(&*name))0 )?0 ; |
167 | 2.74k | let sub_rendered = fragment |
168 | 2.74k | .content |
169 | 2.74k | .iter() |
170 | 50.4k | .map(|v| match v { |
171 | 24.0k | Component::Constant(v) => Ok(Cow::Borrowed(*v)), |
172 | 10.2k | Component::Variable(v) => { |
173 | 10.2k | let variable = variables.get(v.name).ok_or_else(|| Error::VariableNotFound(v.name.into())0 )?0 ; |
174 | 10.2k | if let Some(function136 ) = v.function { Branch (174:32): [True: 136, False: 10.0k]
Branch (174:32): [True: 0, False: 4]
|
175 | 136 | let variable = function(variable); |
176 | 136 | Ok(variable) |
177 | | } else { |
178 | 10.1k | Ok(Cow::Borrowed(&**variable)) |
179 | | } |
180 | | } |
181 | 16.1k | Component::NewLine => Ok(Cow::Borrowed("\n")), |
182 | 50.4k | }) |
183 | 2.74k | .collect::<Result<Vec<Cow<str>>, Error>>()?0 |
184 | 2.74k | .join(""); |
185 | 2.74k | if fragment.mode == FragmentMode::Inline { Branch (185:16): [True: 191, False: 2.55k]
Branch (185:16): [True: 0, False: 1]
|
186 | 191 | rendered.push(sub_rendered.trim().into()); |
187 | 2.55k | } else { |
188 | 2.55k | rendered.push(sub_rendered); |
189 | 2.55k | } |
190 | | } |
191 | 2.50k | Ok(rendered.join("")) |
192 | 2.50k | } |
193 | | |
194 | 1.70k | pub fn scope(&self) -> Scope { |
195 | 1.70k | Scope { |
196 | 1.70k | template: self, |
197 | 1.70k | variables: self.variables.clone(), |
198 | 1.70k | } |
199 | 1.70k | } |
200 | | |
201 | 487 | pub fn render(&self, path: &str, fragments: &[&str]) -> Result<String, Error> { |
202 | 487 | self.render_internal(&self.variables, path, fragments) |
203 | 487 | } |
204 | | } |
205 | | |
206 | | #[derive(Clone)] |
207 | | pub struct Scope<'a, 'fragment, 'variable> { |
208 | | template: &'a Template<'fragment, 'variable>, |
209 | | variables: HashMap<&'variable str, Cow<'variable, str>>, |
210 | | } |
211 | | |
212 | | impl<'variable> Scope<'_, '_, 'variable> { |
213 | 4.59k | pub fn var(&mut self, key: &'variable str, value: impl Into<Cow<'variable, str>>) -> &mut Self { |
214 | 4.59k | self.variables.insert(key, value.into()); |
215 | 4.59k | self |
216 | 4.59k | } |
217 | | |
218 | 3.23k | pub fn var_d(&mut self, key: &'variable str, value: impl ToString) -> &mut Self { |
219 | 3.23k | self.variables.insert(key, value.to_string().into()); |
220 | 3.23k | self |
221 | 3.23k | } |
222 | | |
223 | 413 | pub fn render_to_var(&mut self, path: &str, fragments: &[&str], key: &'variable str) -> Result<&mut Self, Error> { |
224 | 413 | let str = self.template.render_internal(&self.variables, path, fragments)?0 ; |
225 | 413 | self.variables.insert(key, str.into()); |
226 | 413 | Ok(self) |
227 | 413 | } |
228 | | |
229 | 1.60k | pub fn render(&self, path: &str, fragments: &[&str]) -> Result<String, Error> { |
230 | 1.60k | self.template.render_internal(&self.variables, path, fragments) |
231 | 1.60k | } |
232 | | } |